/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * 系统日志记录器（syslogger）在 Postgres 8.0 中出现。它通过重定向到管道来捕获 Postmaster、后端和其他子进程的所有 stderr 输出，并将其写入一组日志文件。在 postgresql.conf 中可以配置日志文件的大小和年龄限制。如果达到或超过这些限制，则当前日志文件将关闭并创建一个新文件（轮换）。日志文件存储在一个子目录中（在 postgresql.conf 中可配置），使用用户可选择的命名方案。
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/syslogger.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>

#include "common/file_perm.h"
#include "lib/stringinfo.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "nodes/pg_list.h"
#include "pgstat.h"
#include "pgtime.h"
#include "port/pg_bitutils.h"
#include "postmaster/fork_process.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/dsm.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pg_shmem.h"
#include "tcop/tcopprot.h"
#include "utils/guc.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
#ifdef FDD
#include "access/xlog.h"
#include "common/md5.h"
#endif //FDD

/*
 * 我们在一个临时缓冲区中读取两倍于块大小的内容，以便在处理后可以将任何剩余片段移到前面，并且我们仍然有空间读取完整的块。
 */
#define READ_BUF_SIZE (2 * PIPE_CHUNK_SIZE)

/* 日志轮换信号文件路径，相对于 $PGDATA */
#define LOGROTATE_SIGNAL_FILE	"logrotate"


/*
 * GUC 参数。logging_collector 不能在 postmaster 启动后更改，但其余的可以在 SIGHUP 时更改。
 */
bool		Logging_collector = false;
int			Log_RotationAge = HOURS_PER_DAY * MINS_PER_HOUR;
int			Log_RotationSize = 10 * 1024;
char	   *Log_directory = NULL;
char	   *Log_filename = NULL;
bool		Log_truncate_on_rotation = false;
int			Log_file_mode = S_IRUSR | S_IWUSR;

extern bool redirection_done;

/*
 * 私有状态
 */
static pg_time_t next_rotation_time;
static bool pipe_eof_seen = false;
static bool rotation_disabled = false;
static FILE *syslogFile = NULL;
static FILE *csvlogFile = NULL;
static FILE *jsonlogFile = NULL;
NON_EXEC_STATIC pg_time_t first_syslogger_file_time = 0;
static char *last_sys_file_name = NULL;
static char *last_csv_file_name = NULL;
static char *last_json_file_name = NULL;
#ifdef FDD
#define FDB_AUDIT_LOG_ENABLED(enbstr) (NULL != (enbstr) && ((enbstr)[0] == '1' || (enbstr)[0] == '2') && (enbstr)[1] == 0)
static int g_fdbAuditLogRotationSize = FDB_DEFAULT_AUDIT_LOG_ROTAT_SIZE;
static int g_fdbAuditLogRotationAge = FDB_DEFAULT_AUDIT_LOG_ROTAT_AGE;
static int g_fdbAuditLogEncryptType = FDB_ENCRYPT_TYPE_NONE;
static char *g_fdbAuditLogDirectory = NULL;
static char *g_fdbAuditLogFilenameFormat = NULL;
static FILE *g_fdbAuditFile = NULL;
static char * g_fdbRc4SecurityKey = NULL;
static struct fdb_cipher_state g_fdbRc4State;
static bool g_fdbRotationDisabled = false;
static bool g_fdbAuditLogEnabled = false;
#endif //FDD

/*
 * 保存来自不同后端部分消息的缓冲区。
 *
 * 保持 NBUFFER_LISTS 个此类列表，其中给定源 PID 的条目位于编号为 (pid % NBUFFER_LISTS) 的列表中，
 * 以减少我们必须检查的任何一个传入消息的条目数量。同一源 PID 永远不能有超过一个条目。
 *
 * 非活动缓冲区不会被从其列表中移除，只是保留以供再次使用。
 * 非活动缓冲区的 PID == 0，数据内容未定义。
 */
typedef struct
{
	int32		pid;			/* 源进程的 PID */
	StringInfoData data;		/* 累积数据，作为 StringInfo */
} save_buffer;

#define NBUFFER_LISTS 256
static List *buffer_lists[NBUFFER_LISTS];

/* 这些必须为 EXEC_BACKEND 案例导出... 令人恼火 */
#ifndef WIN32
int			syslogPipe[2] = {-1, -1};
#else
HANDLE		syslogPipe[2] = {0, 0};
#endif

#ifdef WIN32
static HANDLE threadHandle = 0;
static CRITICAL_SECTION sysloggerSection;
#endif

/*
 * 中断处理程序设置的标志，以便在主循环中进行后续处理。
 */
static volatile sig_atomic_t rotation_requested = false;


/* 本地子例程 */
#ifdef EXEC_BACKEND
static int	fc_syslogger_fdget(FILE *fc_file);
static FILE *fc_syslogger_fdopen(int fc_fd);
static pid_t fc_syslogger_forkexec(void);
static void fc_syslogger_parseArgs(int fc_argc, char *fc_argv[]);
#endif
NON_EXEC_STATIC void fc_SysLoggerMain(int fc_argc, char *fc_argv[]) pg_attribute_noreturn();
static void fc_process_pipe_input(char *fc_logbuffer, int *fc_bytes_in_logbuffer);
static void fc_flush_pipe_input(char *fc_logbuffer, int *fc_bytes_in_logbuffer);
static FILE *fc_logfile_open(const char *fc_filename, const char *fc_mode,
						  bool fc_allow_errors);

#ifdef WIN32
static unsigned int __stdcall pipeThread(void *arg);
#endif
static void fc_logfile_rotate(bool fc_time_based_rotation, int fc_size_rotation_for);
static bool fc_logfile_rotate_dest(bool fc_time_based_rotation,
								int fc_size_rotation_for, pg_time_t fc_fntime,
								int fc_target_dest, char **fc_last_file_name,
								FILE **fc_logFile);
static char *fc_logfile_getname(pg_time_t fc_timestamp, const char *fc_suffix);
static void set_next_rotation_time(void);
static void fc_sigUsr1Handler(SIGNAL_ARGS);
static void update_metainfo_datafile(void);


#ifdef FDD
int fdb_fillAuditLogEncodeState(struct fdb_cipher_state *fc_pState, FDB_ENCRYPT_TYPE fc_type, const unsigned char *fc_key, int fc_keyLen)
{
	static uint64 	fc_sysUniqueId = 0;
	unsigned char	fc_buf[33];

	if(fc_sysUniqueId == 0)
	{
		if(MyBackendType == B_LOGGER)
		{
			LocalProcessControlFile(true);
		}
		fc_sysUniqueId = GetSystemIdentifier();
		if(fc_sysUniqueId == 0)
		{
			fc_sysUniqueId = 1;
		}
	}
	if(NULL == fc_key)
	{
		fc_key = (const unsigned char*)FDB_DEFAULT_AUDIT_SEC_KEY;
		fc_keyLen = strlen(FDB_DEFAULT_AUDIT_SEC_KEY);
	}
	/* 对密钥采用数据库SystemIdentifie进行rc加密 */
	fdb_cipher_init(fc_pState, FDB_ENCRYPT_TYPE_RC4, (const unsigned char*)&fc_sysUniqueId, sizeof(fc_sysUniqueId), NULL);
	if(!fdb_md5_hash(fc_key, fc_keyLen, (char*)fc_buf))
	{
		return 1;
	}
	fdb_encrypt(fc_pState, fc_buf, fc_buf, 32);
	
	if(FDB_ENCRYPT_TYPE_SM4 == fc_type)
	{
		/* sm4加密时，选取buf的前16字节作为iv值,审计日志暂时不支持该功能，所以不会进入该流程 */
		Assert(FDB_ENCRYPT_TYPE_SM4 != fc_type);
		fdb_cipher_init(fc_pState, fc_type, fc_buf, 32, fc_buf);
	}
	else
	{
		fdb_cipher_init(fc_pState, fc_type, fc_buf, 32, NULL);
	}
	return 0;
}

static bool FDB_dealUpdateAuditConf(void)
{
	bool		fc_succ = false;
	bool		fc_bRet = false;
	char		*fc_str = NULL;
	int			fc_iValue = 0;
	int			fc_len = 0, fc_i = 0;

	fc_str = GetConfigOptionByName(FDB_ENABLE_AUDIT, NULL, true);
	g_fdbAuditLogEnabled = FDB_AUDIT_LOG_ENABLED(fc_str);
	if(!g_fdbAuditLogEnabled)
	{
		return false;
	}
	fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_DIR, NULL, true);
	if(NULL == fc_str)
	{
		fc_str = FDB_DEFAULT_AUDIT_LOG_DIR;
	}
	if(g_fdbAuditLogDirectory == NULL || strcmp(g_fdbAuditLogDirectory, fc_str) != 0)
	{
		if(NULL != g_fdbAuditLogDirectory)
		{
			pfree(g_fdbAuditLogDirectory);
		}
		g_fdbAuditLogDirectory = pstrdup(fc_str);
		MakePGDirectory(g_fdbAuditLogDirectory);
		fc_bRet = true;//外部切换文件
	}

	fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_FILENAME_PREFIX, NULL, true);
	if(NULL == fc_str)
	{
		fc_str = FDB_DEFAULT_AUDIT_LOG_FILENAME_PREFIX;
	}
	fc_len = strlen(fc_str);
	if(g_fdbAuditLogFilenameFormat != NULL)
	{
		fc_succ = false;
		fc_i = strlen(g_fdbAuditLogFilenameFormat);
		for(; fc_i>=0; fc_i--)
		{
			if(g_fdbAuditLogFilenameFormat[fc_i] == '_')
			{
				if(fc_succ)
				{
					break;
				}
				fc_succ = true;
			}
		}
		if(fc_i != fc_len || memcmp(fc_str, g_fdbAuditLogFilenameFormat, fc_len) != 0)
		{
			pfree(g_fdbAuditLogFilenameFormat);
			g_fdbAuditLogFilenameFormat = NULL;
		}
	}
	if(g_fdbAuditLogFilenameFormat == NULL)
	{
		fc_i = strlen(FDB_AUDIT_LOG_FILENAME_FORMAT);
		g_fdbAuditLogFilenameFormat = palloc(fc_len + fc_i + 1);
		if(NULL != g_fdbAuditLogFilenameFormat)
		{
			memcpy(g_fdbAuditLogFilenameFormat, fc_str, fc_len);
			memcpy(g_fdbAuditLogFilenameFormat+fc_len, FDB_AUDIT_LOG_FILENAME_FORMAT, fc_i+1);
			fc_bRet = true;//外部切换文件
		}
	}

	fc_succ = false;
	fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_AGE_INTERVAL, NULL, true);
	if(NULL != fc_str)
	{
		fc_succ = parse_int(fc_str, &fc_iValue, GUC_UNIT_MIN, NULL);
	}
	if(!fc_succ)
	{
		fc_iValue = FDB_DEFAULT_AUDIT_LOG_ROTAT_AGE;
	}
	g_fdbAuditLogRotationAge = fc_iValue;

	fc_succ = false;
	fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_AGE_SIZE, NULL, true);
	if(NULL != fc_str)
	{
		fc_succ = parse_int(fc_str, &fc_iValue, GUC_UNIT_KB, NULL);
	}
	if(!fc_succ)
	{
		fc_iValue = FDB_DEFAULT_AUDIT_LOG_ROTAT_SIZE;
	}
	g_fdbAuditLogRotationSize = fc_iValue;

	fc_succ = false;
	fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_ENCRYPT_TYPE, NULL, true);
	if(NULL == fc_str)
	{
		fc_str = FDB_DEFAULT_AUDIT_LOG_ENCRYPT_TYPE;
	}
	if(strcmp(fc_str, "plain") == 0)
	{
		if(g_fdbAuditLogEncryptType != FDB_ENCRYPT_TYPE_NONE)
		{
			g_fdbAuditLogEncryptType = FDB_ENCRYPT_TYPE_NONE;
			fc_bRet = true;
		}
	}
	else if(strcmp(fc_str, "xor") == 0)
	{
		if(g_fdbAuditLogEncryptType != FDB_ENCRYPT_TYPE_XOR)
		{
			g_fdbAuditLogEncryptType = FDB_ENCRYPT_TYPE_XOR;
			fc_bRet = true;
		}
	}
	else if(strcmp(fc_str, "rc4") == 0)
	{
		if(g_fdbAuditLogEncryptType != FDB_ENCRYPT_TYPE_RC4)
		{
			g_fdbAuditLogEncryptType = FDB_ENCRYPT_TYPE_RC4;
			fc_bRet = true;
		}
	}
	else
	{
		if(g_fdbAuditLogEncryptType != FDB_ENCRYPT_TYPE_NONE)
		{
			g_fdbAuditLogEncryptType = FDB_ENCRYPT_TYPE_NONE;
			fc_bRet = true;
		}
	}
	
	if(g_fdbAuditLogEncryptType == FDB_ENCRYPT_TYPE_NONE)
	{
		g_fdbRc4SecurityKey = pstrdup(FDB_PLAIN_AUDIT_LOG_HEAD_MD5STR);
	}
	else
	{
		fc_str = GetConfigOptionByName(FDB_AUDIT_LOG_ENCRYPT_KEY, NULL, true);
		if(NULL == fc_str)
		{
			fc_str = FDB_DEFAULT_AUDIT_SEC_KEY;
		}
		if(g_fdbRc4SecurityKey == NULL || strcmp(g_fdbRc4SecurityKey, fc_str) != 0)
		{
			if(NULL != g_fdbRc4SecurityKey)
			{
				pfree(g_fdbRc4SecurityKey);
			}
			g_fdbRc4SecurityKey = pstrdup(fc_str);
			fc_bRet = true;//外部切换文件
		}
	}
	return fc_bRet;
}

static bool FDB_auditLogfileRotate(bool fc_allow_errors)
{
	char	   *fc_filename = NULL;
	FILE	   *fc_fh = NULL;
	pg_time_t	fc_fntime = 0;
	mode_t		fc_oumask = 0;
	int			fc_len = 0;
	char 		fc_md5[33];

	if(NULL == g_fdbRc4SecurityKey || !fdb_md5_hash(g_fdbRc4SecurityKey, strlen(g_fdbRc4SecurityKey), fc_md5))
	{
		if(fc_allow_errors)
		{
			g_fdbRotationDisabled = true;
			ereport(LOG, (errmsg("failed rotate audit log file, disabling automatic rotation (use SIGHUP to re-enable)")));
		}
		else
		{
			ereport(FATAL, (errmsg("md5 hash faield")));
		}
		return false;
	}
	Assert(MAXPGPATH > 16);
	fc_filename = palloc(MAXPGPATH);
	if(NULL != fc_filename)
	{
		fc_fntime = time(NULL);
		snprintf(fc_filename, MAXPGPATH, "%s/", g_fdbAuditLogDirectory);
		fc_len = strlen(fc_filename);
		pg_strftime(fc_filename + fc_len, MAXPGPATH - fc_len, g_fdbAuditLogFilenameFormat, pg_localtime(&fc_fntime, log_timezone));
		//pg_strftime(filename + len, MAXPGPATH - len, "audit.log", pg_localtime(&fntime, log_timezone));

		fc_oumask = umask((mode_t) ((~(Log_file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO)));
		fc_fh = fopen(fc_filename, "wb");
		umask(fc_oumask);

		if(NULL == fc_fh)
		{
			if(fc_allow_errors)
			{
				g_fdbRotationDisabled = true;
				ereport(LOG, (errmsg("failed rotate audit log file, disabling automatic rotation (use SIGHUP to re-enable)")));
			}
			else
			{
				ereport(FATAL, (errcode_for_file_access(), errmsg("could not open log file \"%s\": %m", fc_filename)));
			}
			pfree(fc_filename);
			return false;
		}
		setvbuf(fc_fh, NULL, _IOFBF, 0);
		if(NULL != g_fdbAuditFile)
		{
			fclose(g_fdbAuditFile);
		}
		fdb_fillAuditLogEncodeState(&g_fdbRc4State,g_fdbAuditLogEncryptType, (const unsigned char*)g_fdbRc4SecurityKey, strlen(g_fdbRc4SecurityKey));
		if(FDB_IS_AYDIT_ENCRYPT(g_fdbAuditLogEncryptType))
		{
			fdb_encrypt(&g_fdbRc4State, (unsigned char*)fc_md5, (unsigned char*)fc_md5, 32);
		}
		
		fc_len = fwrite(fc_md5, 1, 32, fc_fh);
		if(fc_len != 32)
		{
			g_fdbRotationDisabled = true;
			g_fdbAuditFile = NULL;
			if(fc_allow_errors)
			{
				ereport(LOG, (errmsg("failed rotate audit log file, disabling automatic rotation (use SIGHUP to re-enable)")));
			}
			else
			{
				ereport(FATAL, (errcode_for_file_access(), errmsg("write audit log file failed.")));
			}
			fclose(fc_fh);
			unlink(fc_filename);
			pfree(fc_filename);
			return false;
		}
		g_fdbAuditFile = fc_fh;
		pfree(fc_filename);
		return true;
	}
	return false;
}
#endif //FDD
/*
 * syslogger 进程的主入口点
 * argc/argv 参数仅在 EXEC_BACKEND 案例中有效。
 */
NON_EXEC_STATIC void fc_SysLoggerMain(int fc_argc, char *fc_argv[])
{
#ifndef WIN32
	char		fc_logbuffer[READ_BUF_SIZE];
	int			fc_bytes_in_logbuffer = 0;
#endif
	char	   *fc_currentLogDir;
	char	   *fc_currentLogFilename;
	int			fc_currentLogRotationAge;
	pg_time_t	fc_now;
	WaitEventSet *fc_wes;
#ifdef FDD
	bool		fc_rotationAuditLogFile = true;
	pg_time_t 	fc_nextRotationAuditLogFileTime = 0;
#endif

	fc_now = MyStartTime;

#ifdef EXEC_BACKEND
	fc_syslogger_parseArgs(fc_argc, fc_argv);
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */

	MyBackendType = B_LOGGER;
	init_ps_display(NULL);

	/*
	 * 如果我们重启了，我们的 stderr 已经重定向到我们自己的输入管道。
	 * 这当然是非常无用的，更不用说这干扰了检测管道 EOF。将 stderr 指向 /dev/null。这
	 * 假设 syslogger 中生成的所有有趣消息都将通过 elog.c 并发送到 write_syslogger_file。
	 */
	if (redirection_done)
	{
		int			fc_fd = open(DEVNULL, O_WRONLY, 0);

		/*
		 * 关闭操作看起来可能是多余的，但并非如此：我们想确保
		 * 在打开失败的情况下也关闭管道。我们可以
		 * 在 stderr 指向无处的情况下生存，但我们无法承受
		 * 额外的管道输入描述符悬而未决。
		 *
		 * 由于我们只是试图将这些重置为 DEVNULL，检查 close/dup2 调用的失败没有什么意义，
		 * 如果它们失败，则假设文件描述符被关闭，任何写入无论如何都会进入位桶。
		 */
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
		if (fc_fd != -1)
		{
			(void) dup2(fc_fd, STDOUT_FILENO);
			(void) dup2(fc_fd, STDERR_FILENO);
			close(fc_fd);
		}
	}

	/*
	 * syslogger 自身的 stderr 不能是 syslogPipe，因此如果我们没有刚刚关闭它，则将其设置回文本模式。
	 * （在 SubPostmasterMain 中已设置为二进制模式）。
	 */
#ifdef WIN32
	else
		_setmode(STDERR_FILENO, _O_TEXT);
#endif

	/*
	 * 还要关闭我们自己的管道写入端的副本。这是为了
	 * 确保我们可以正确检测管道 EOF。
	 * （但请注意，在重启的情况下，postmaster 已经完成了这项工作。）
	 */
#ifndef WIN32
	if (syslogPipe[1] >= 0)
		close(syslogPipe[1]);
	syslogPipe[1] = -1;
#else
	if (syslogPipe[1])
		CloseHandle(syslogPipe[1]);
	syslogPipe[1] = 0;
#endif

	/*
	 * 正确接受或忽略 postmaster 可能发送给我们的信号
	 *
	 * 注意：我们忽略所有终止信号，而是仅在所有
	 * 上游进程消失时退出，以确保我们不会错过任何破损后端的垂死挣扎...
	 */

	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* 设置标志以读取配置
													 * 文件 */
	pqsignal(SIGINT, SIG_IGN);
	pqsignal(SIGTERM, SIG_IGN);
	pqsignal(SIGQUIT, SIG_IGN);
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, fc_sigUsr1Handler);	/* 请求日志轮换 */
	pqsignal(SIGUSR2, SIG_IGN);

	/*
	 * 重置一些由postmaster接受但这里不接受的信号
	 */
	pqsignal(SIGCHLD, SIG_DFL);

	PG_SETMASK(&UnBlockSig);

#ifdef WIN32
	/* 启动单独的数据传输线程 */
	InitializeCriticalSection(&sysloggerSection);
	EnterCriticalSection(&sysloggerSection);

	threadHandle = (HANDLE) _beginthreadex(NULL, 0, pipeThread, NULL, 0, NULL);
	if (threadHandle == 0)
		elog(FATAL, "could not create syslogger data transfer thread: %m");
#endif							/* WIN32 */

	/*
	 * 记住活动日志文件的名称。我们从参考
	 * 时间重新计算这些，因为在 EXEC_BACKEND 案例中，仅传递 pg_time_t 比传递整个文件路径便宜得多。
	 */
	last_sys_file_name = fc_logfile_getname(first_syslogger_file_time, NULL);
	if (csvlogFile != NULL)
		last_csv_file_name = fc_logfile_getname(first_syslogger_file_time, ".csv");
	if (jsonlogFile != NULL)
		last_json_file_name = fc_logfile_getname(first_syslogger_file_time, ".json");

	/* 记住活动日志文件参数 */
	fc_currentLogDir = pstrdup(Log_directory);
	fc_currentLogFilename = pstrdup(Log_filename);
	fc_currentLogRotationAge = Log_RotationAge;
	/* 设置下一个计划的轮换时间 */
	set_next_rotation_time();
	update_metainfo_datafile();

	/*
	 * 重置 whereToSendOutput，因为 postmaster 将执行此操作（但在我们 fork 的时候尚未执行）。
	 * 这防止 syslogger 本身的消息重复输出。
	 */
	whereToSendOutput = DestNone;

	/*
	 * 设置一个可重用的 WaitEventSet 对象，我们将用于等待我们的闩锁，
	 * 并且（除了在 Windows 上）我们的套接字。
	 *
	 * 与所有其他 postmaster 子进程不同，我们将忽略 postmaster
	 * 的死亡，因为我们想从所有后端收集最终日志输出，然后最后退出。
	 * 我们将通过运行直到在 syslog 管道上看到 EOF 来做到这一点，这意味着所有其他后端都已退出
	 * （包括 postmaster）。
	 */
	fc_wes = CreateWaitEventSet(CurrentMemoryContext, 2);
	AddWaitEventToSet(fc_wes, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch, NULL);
#ifndef WIN32
	AddWaitEventToSet(fc_wes, WL_SOCKET_READABLE, syslogPipe[0], NULL, NULL);
#endif
#ifdef FDD
	rotation_disabled = !Logging_collector;
#endif
	/* 主要工作循环 */
	for (;;)
	{
		bool		fc_time_based_rotation = false;
		int			fc_size_rotation_for = 0;
		long		fc_cur_timeout;
		WaitEvent	fc_event;

#ifndef WIN32
		int			fc_rc;
#endif

		/* 清除任何已经等待的唤醒 */
		ResetLatch(MyLatch);

		/*
		 * 处理最近收到的任何请求或信号。
		 */
		if (ConfigReloadPending)
		{
			ConfigReloadPending = false;
			ProcessConfigFile(PGC_SIGHUP);

			/*
			 * 检查 postgresql.conf 中的日志目录或文件名模式是否改变。
			 * 如果改变了，强制旋转以确保我们在正确的位置写入日志文件。
			 */
			if (strcmp(Log_directory, fc_currentLogDir) != 0)
			{
				pfree(fc_currentLogDir);
				fc_currentLogDir = pstrdup(Log_directory);
				rotation_requested = true;

				/*
				 * 如果目录不存在，则创建新目录；忽略错误
				 */
				(void) MakePGDirectory(Log_directory);
			}
			if (strcmp(Log_filename, fc_currentLogFilename) != 0)
			{
				pfree(fc_currentLogFilename);
				fc_currentLogFilename = pstrdup(Log_filename);
				rotation_requested = true;
			}

			/*
			 * 如果刚刚开启或关闭了 CSVLOG 输出，强制进行旋转
			 * 以便相应地打开或关闭 csvlogFile。
			 */
			if (((Log_destination & LOG_DESTINATION_CSVLOG) != 0) !=
				(csvlogFile != NULL))
				rotation_requested = true;

			/*
			 * 如果刚刚开启或关闭了 JSONLOG 输出，
			 * 强制旋转并相应地打开或关闭 jsonlogFile。
			 */
			if (((Log_destination & LOG_DESTINATION_JSONLOG) != 0) !=
				(jsonlogFile != NULL))
				rotation_requested = true;

			/*
			 * 如果旋转时间参数改变，重置下一个旋转时间，
			 * 但不要立即强制旋转。
			 */
			if (fc_currentLogRotationAge != Log_RotationAge)
			{
				fc_currentLogRotationAge = Log_RotationAge;
				set_next_rotation_time();
			}

			/*
			 * 如果发生了禁用旋转的故障，在 SIGHUP 之后重新启用旋转
			 * 尝试，并立即强制一个。
			 */
			if (rotation_disabled)
			{
				rotation_disabled = false;
				rotation_requested = true;
			}

			/*
			 * 在重新加载配置时强制重写最后的日志文件名。
			 * 即使 rotation_requested 为 false，log_destination 也可能已
			 * 被更改，我们不想等到下一个文件旋转。
			 */
			update_metainfo_datafile();
#ifdef FDD
			if(FDB_dealUpdateAuditConf())
			{
				fc_rotationAuditLogFile = true;
			}
			g_fdbRotationDisabled = false;
			if(!Logging_collector)
			{
				rotation_disabled = true;
				rotation_requested = false;
			}
#endif //FDD
		}

		if (Log_RotationAge > 0 && !rotation_disabled)
		{
			/* 如果到了时间，进行日志文件旋转 */
			fc_now = (pg_time_t) time(NULL);
			if (fc_now >= next_rotation_time)
				rotation_requested = fc_time_based_rotation = true;
		}

		if (!rotation_requested && Log_RotationSize > 0 && !rotation_disabled)
		{
			/* 如果文件太大，进行旋转 */
			if (ftell(syslogFile) >= Log_RotationSize * 1024L)
			{
				rotation_requested = true;
				fc_size_rotation_for |= LOG_DESTINATION_STDERR;
			}
			if (csvlogFile != NULL &&
				ftell(csvlogFile) >= Log_RotationSize * 1024L)
			{
				rotation_requested = true;
				fc_size_rotation_for |= LOG_DESTINATION_CSVLOG;
			}
			if (jsonlogFile != NULL &&
				ftell(jsonlogFile) >= Log_RotationSize * 1024L)
			{
				rotation_requested = true;
				fc_size_rotation_for |= LOG_DESTINATION_JSONLOG;
			}
		}

		if (rotation_requested)
		{
			/*
			 * 当两个值都为零时强制旋转。这意味着请求
			 * 是由 pg_rotate_logfile() 或 "pg_ctl logrotate" 发送的。
			 */
			if (!fc_time_based_rotation && fc_size_rotation_for == 0)
				fc_size_rotation_for = LOG_DESTINATION_STDERR |
					LOG_DESTINATION_CSVLOG |
					LOG_DESTINATION_JSONLOG;
			fc_logfile_rotate(fc_time_based_rotation, fc_size_rotation_for);
		}

		/*
		 * 计算下次基于时间的旋转的时间，以便我们不会
		 * 睡得比这个长。我们假设上面获得的 "now" 值仍然足够接近。
		 * 注意，在调用 logfile_rotate() 之后，我们不能进行此计算，
		 * 因为它会提前 next_rotation_time。
		 *
		 * 还要注意，我们需要防止在计算超时时发生溢出：
		 * 在 Log_RotationAge 的大设置下，next_rotation_time
		 * 可能在未来超过 INT_MAX 毫秒。在这种情况下，我们将
		 * 等待不超过 INT_MAX 毫秒，并重试。
		 */
		if (Log_RotationAge > 0 && !rotation_disabled)
		{
			pg_time_t	fc_delay;

			fc_delay = next_rotation_time - fc_now;
			if (fc_delay > 0)
			{
				if (fc_delay > INT_MAX / 1000)
					fc_delay = INT_MAX / 1000;
				fc_cur_timeout = fc_delay * 1000L;	/* msec */
			}
			else
				fc_cur_timeout = 0;
		}
		else
			fc_cur_timeout = -1L;
#ifdef FDD
		if(NULL != fdb_write_audit_hook && !g_fdbRotationDisabled && g_fdbAuditLogEnabled)
		{
			if(g_fdbAuditLogRotationAge > 0)
			{
				fc_now = (pg_time_t) time(NULL);
				if (fc_now >= fc_nextRotationAuditLogFileTime)
				{
					fc_rotationAuditLogFile = true;
				}
			}
			if(!fc_rotationAuditLogFile && (g_fdbAuditFile == NULL || (g_fdbAuditLogRotationSize > 0 && ftell(g_fdbAuditFile) >= (g_fdbAuditLogRotationSize << 10))))
			{
				fc_rotationAuditLogFile = true;
			}
			if(fc_rotationAuditLogFile)
			{
				if(FDB_auditLogfileRotate(true))
				{
					if(g_fdbAuditLogRotationAge > 0)
					{
						fc_nextRotationAuditLogFileTime = ((pg_time_t) time(NULL)) + g_fdbAuditLogRotationAge*SECS_PER_MINUTE;
					}
					fc_rotationAuditLogFile = false;
				}
			}
			if(g_fdbAuditLogRotationAge > 0)
			{
				pg_time_t	fc_delay;

				fc_delay = fc_nextRotationAuditLogFileTime - fc_now;
				if(fc_delay > 0)
				{
					if (fc_delay > (INT_MAX / 1000))
					{
						fc_delay = INT_MAX;
					}
					else
					{
						fc_delay = fc_delay * 1000;
					}
					if(fc_delay < fc_cur_timeout)
					{
						fc_cur_timeout = fc_delay;
					}
				}
				else
				{
					fc_cur_timeout = 0;
				}
			}
		}
#endif //FDD
		/*
		 * 睡眠直到有事情要做
		 */
#ifndef WIN32
		fc_rc = WaitEventSetWait(fc_wes, fc_cur_timeout, &fc_event, 1,
							  WAIT_EVENT_SYSLOGGER_MAIN);

		if (fc_rc == 1 && fc_event.events == WL_SOCKET_READABLE)
		{
			int			fc_bytesRead;

			fc_bytesRead = read(syslogPipe[0],
							 fc_logbuffer + fc_bytes_in_logbuffer,
							 sizeof(fc_logbuffer) - fc_bytes_in_logbuffer);
			if (fc_bytesRead < 0)
			{
				if (errno != EINTR)
					ereport(LOG,
							(errcode_for_socket_access(),
							 errmsg("could not read from logger pipe: %m")));
			}
			else if (fc_bytesRead > 0)
			{
				fc_bytes_in_logbuffer += fc_bytesRead;
				fc_process_pipe_input(fc_logbuffer, &fc_bytes_in_logbuffer);
				continue;
			}
			else
			{
				/*
				 * 当 select() 表示可以读取时读取的字节为零，意味着
				 * 管道的 EOF：也就是说，没有任何进程
				 * 正在打开管道的写入端。因此，后端进程和所有的
				 * 后端进程被关闭，我们的工作完成。
				 */
				pipe_eof_seen = true;

				/* 如果还有任何数据，则立即强制输出 */
				fc_flush_pipe_input(fc_logbuffer, &fc_bytes_in_logbuffer);
			}
		}
#else							/* WIN32 */

		/*
		 * 在 Windows 上，我们将数据传输和管道 EOF 的检测
		 * 留给一个单独的线程。主线程只在处理 SIGHUP
		 * 和旋转条件时醒来。
		 *
		 * 服务器代码通常不是线程安全的，因此我们确保在
		 * 不睡眠时通过进入临界区来确保同时只有一个线程处于活动状态。
		 */
		LeaveCriticalSection(&sysloggerSection);

		(void) WaitEventSetWait(wes, cur_timeout, &event, 1,
								WAIT_EVENT_SYSLOGGER_MAIN);

		EnterCriticalSection(&sysloggerSection);
#endif							/* WIN32 */

		if (pipe_eof_seen)
		{
			/*
			 * 在真实的 stderr 上看到此消息令人烦恼 - 所以我们将其设置为
			 * DEBUG1 以在正常使用中抑制。
			 */
			ereport(DEBUG1,
					(errmsg_internal("logger shutting down")));

			/*
			 * syslogger 的正常退出在此。请注意，我们
			 * 故意不在退出之前关闭 syslogFile；这是为了
			 * 允许在 proc_exit 内部生成 elog 消息的可能性。
			 * 常规的 exit() 将负责刷新
			 * 和关闭 stdio 通道。
			 */
			proc_exit(0);
		}
	}
}

/*
 * 启动 syslogger 子进程的 postmaster 子例程。
 */
int SysLogger_Start(void)
{
	pid_t		fc_sysloggerPid;
	char	   *fc_filename;
#ifdef FDD
	const char *fc_auditEnableConfStr = GetConfigOptionByName(FDB_ENABLE_AUDIT, NULL, true);

	g_fdbAuditLogEnabled = FDB_AUDIT_LOG_ENABLED(fc_auditEnableConfStr);
	if (!Logging_collector)
	{
		if(!g_fdbAuditLogEnabled)
		{
			//审计功能未开启
			return 0;
		}
		//到这里，表示未开启独立日志进程功能，而开启了审计功能
	}
#else
	if (!Logging_collector)
		return 0;
#endif
	/*
	 * 如果是第一次，创建将接收 stderr
	 * 输出的管道。
	 *
	 * 如果 syslogger 崩溃并需要重新启动，我们将继续使用
	 * 相同的管道（实际上必须这样做，因为现存的后端将在
	 * 该管道中写入）。
	 *
	 * 这意味着 postmaster 必须继续保持管道的读取端打开，
	 * 以便我们能够将其传递给重生的 syslogger。这
	 * 有点麻烦，但我们别无选择。
	 *
	 * 还要注意，我们不麻烦通过调用
	 * Reserve/ReleaseExternalFD 来计数管道的 FD。
	 * 在 postmaster 或 syslogger 进程中没有真实的需要
	 * 准确记录它们，并且管道的两端将在所有其他 postmaster 子进程中被关闭。
	 */
#ifndef WIN32
	if (syslogPipe[0] < 0)
	{
		if (pipe(syslogPipe) < 0)
			ereport(FATAL,
					(errcode_for_socket_access(),
					 errmsg("could not create pipe for syslog: %m")));
	}
#else
	if (!syslogPipe[0])
	{
		SECURITY_ATTRIBUTES sa;

		memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
		sa.nLength = sizeof(SECURITY_ATTRIBUTES);
		sa.bInheritHandle = TRUE;

		if (!CreatePipe(&syslogPipe[0], &syslogPipe[1], &sa, 32768))
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not create pipe for syslog: %m")));
	}
#endif
#ifdef FDD
	if(g_fdbAuditLogEnabled)
	{
		//审计日志启用了
		FDB_dealUpdateAuditConf();
		FDB_auditLogfileRotate(false);
	}
	if(Logging_collector || g_fdbAuditLogEnabled)
	{
#endif
	/*
	 * 如果目录不存在，则创建日志目录；忽略错误
	 */
	(void) MakePGDirectory(Log_directory);

	/*
	 * 初始日志文件直接在 postmaster 中创建，以验证
	 * Log_directory 是否可写。我们保存参考时间，以便
	 * syslogger 子进程能够重新计算此文件名。
	 *
	 * 在 syslogger 重启期间重新执行此操作可能看起来有点奇怪，
	 * 但我们必须这样做，因为 postmaster 在上一个 fork 之后关闭了 syslogFile
	 * （并且记住旧文件也是不对的）。请注意，我们在这里始终附加，
	 * 不会覆盖任何现有文件。这与正常规则是一致的，
	 * 因为按定义这不是基于时间的旋转。
	 */
	first_syslogger_file_time = time(NULL);

	fc_filename = fc_logfile_getname(first_syslogger_file_time, NULL);

	syslogFile = fc_logfile_open(fc_filename, "a", false);

	pfree(fc_filename);

	/*
	 * 对于初始 CSV 日志文件也是如此，如果启用了该功能。
	 * （请注意，即使仅名义上启用了 CSV 输出，我们也会打开 syslogFile，
	 * 因为一些代码路径无论如何会写入 syslogFile。）
	 */
	if (Log_destination & LOG_DESTINATION_CSVLOG)
	{
		fc_filename = fc_logfile_getname(first_syslogger_file_time, ".csv");

		csvlogFile = fc_logfile_open(fc_filename, "a", false);

		pfree(fc_filename);
	}

	
/*
	 * 如果启用了初始 JSON 日志文件情况也是如此。 （注意，尽管仅名义上启用了 JSON 输出，但我们仍然会打开 syslogFile，因为某些代码路径无论如何都会写入 syslogFile。）
	 */
	if (Log_destination & LOG_DESTINATION_JSONLOG)
	{
		fc_filename = fc_logfile_getname(first_syslogger_file_time, ".json");

		jsonlogFile = fc_logfile_open(fc_filename, "a", false);

		pfree(fc_filename);
	}

#ifdef FDD
	}
#endif //FDD
#ifdef EXEC_BACKEND
	switch ((fc_sysloggerPid = fc_syslogger_forkexec()))
#else
	switch ((fc_sysloggerPid = fork_process()))
#endif
	{
		case -1:
			ereport(LOG,
					(errmsg("could not fork system logger: %m")));
			return 0;

#ifndef EXEC_BACKEND
		case 0:
			/* 在 postmaster 子进程中... */
			InitPostmasterChild();

			/* 关闭 postmaster 的套接字 */
			ClosePostmasterPorts(true);

			/* 同样断开与 postmaster 共享内存的连接 */
			dsm_detach_all();
			PGSharedMemoryDetach();

			/* 执行工作 */
			fc_SysLoggerMain(0, NULL);
			break;
#endif

		default:
			/* 在 postmaster 中成功 */
#ifdef FDD
			if(NULL != g_fdbAuditFile)
			{
				fclose(g_fdbAuditFile);
				g_fdbAuditFile = NULL;
			}
			if(Logging_collector)
			{
#endif //FDD
			/* 现在如果还没有完成，我们重定向 stderr */
			if (!redirection_done)
			{
#ifdef WIN32
				int			fd;
#endif

				/*
				 * 在重定向时留下一条面包屑路径，以防用户
				 * 忘记重定向是活动的并且只查看原始 stderr 目标文件。
				 */
				ereport(LOG,
						(errmsg("redirecting log output to logging collector process"),
						 errhint("Future log output will appear in directory \"%s\".",
								 Log_directory)));

#ifndef WIN32
				fflush(stdout);
				if (dup2(syslogPipe[1], STDOUT_FILENO) < 0)
					ereport(FATAL,
							(errcode_for_file_access(),
							 errmsg("could not redirect stdout: %m")));
				fflush(stderr);
				if (dup2(syslogPipe[1], STDERR_FILENO) < 0)
					ereport(FATAL,
							(errcode_for_file_access(),
							 errmsg("could not redirect stderr: %m")));
				/* 现在我们已经完成了管道的写入端。 */
				close(syslogPipe[1]);
				syslogPipe[1] = -1;
#else

				/*
				 * 以二进制模式打开管道，并确保在重复进入后，stderr 为二进制
				 * 以避免干扰管道分块协议。
				 */
				fflush(stderr);
				fd = _open_osfhandle((intptr_t) syslogPipe[1],
									 _O_APPEND | _O_BINARY);
				if (dup2(fd, STDERR_FILENO) < 0)
					ereport(FATAL,
							(errcode_for_file_access(),
							 errmsg("could not redirect stderr: %m")));
				close(fd);
				_setmode(STDERR_FILENO, _O_BINARY);

				/*
				 * 现在我们已经完成了管道的写入端。
				 * 不得调用CloseHandle()，因为前面的close()已关闭底层句柄。
				 */
				syslogPipe[1] = 0;
#endif
				redirection_done = true;
			}

			/* postmaster将永远不会写入文件；关闭它们 */
			fclose(syslogFile);
			syslogFile = NULL;
			if (csvlogFile != NULL)
			{
				fclose(csvlogFile);
				csvlogFile = NULL;
			}
			if (jsonlogFile != NULL)
			{
				fclose(jsonlogFile);
				jsonlogFile = NULL;
			}
#ifdef FDD
			}
#endif
			return (int) fc_sysloggerPid;
	}

	/* 我们绝对不应该到达这里 */
	return 0;
}


#ifdef EXEC_BACKEND

/*
 * syslogger_fdget() -
 *
 * 实用程序包装器，用于抓取已打开的错误输出文件的文件描述符。
 * 在构建用于生成日志收集器的命令时使用。
 */
static int fc_syslogger_fdget(FILE *fc_file)
{
#ifndef WIN32
	if (fc_file != NULL)
		return fileno(fc_file);
	else
		return -1;
#else
	if (file != NULL)
		return (int) _get_osfhandle(_fileno(file));
	else
		return 0;
#endif							/* WIN32 */
}

/*
 * syslogger_fdopen() -
 *
 * 实用程序包装器，用于使用给定的文件描述符重新打开错误输出文件。
 * 在解析分叉日志收集器中的参数时使用。
 */
static FILE * fc_syslogger_fdopen(int fc_fd)
{
	FILE	   *fc_file = NULL;

#ifndef WIN32
	if (fc_fd != -1)
	{
		fc_file = fdopen(fc_fd, "a");
		setvbuf(fc_file, NULL, PG_IOLBF, 0);
	}
#else							/* WIN32 */
	if (fd != 0)
	{
		fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
		if (fd > 0)
		{
			file = fdopen(fd, "a");
			setvbuf(file, NULL, PG_IOLBF, 0);
		}
	}
#endif							/* WIN32 */

	return fc_file;
}

/*
 * syslogger_forkexec() -
 *
 * 格式化参数列表，然后生成和执行syslogger进程。
 */
static pid_t fc_syslogger_forkexec(void)
{
	char	   *fc_av[10];
	int			fc_ac = 0;
	char		fc_filenobuf[32];
	char		fc_csvfilenobuf[32];
	char		fc_jsonfilenobuf[32];

	fc_av[fc_ac++] = "postgres";
	fc_av[fc_ac++] = "--forklog";
	fc_av[fc_ac++] = NULL;			/* 由 postmaster_forkexec 填充 */

	/* 静态变量（那些未通过write_backend_variables传递的变量） */
	snprintf(fc_filenobuf, sizeof(fc_filenobuf), "%d",
			 fc_syslogger_fdget(syslogFile));
	fc_av[fc_ac++] = fc_filenobuf;
	snprintf(fc_csvfilenobuf, sizeof(fc_csvfilenobuf), "%d",
			 fc_syslogger_fdget(csvlogFile));
	fc_av[fc_ac++] = fc_csvfilenobuf;
	snprintf(fc_jsonfilenobuf, sizeof(fc_jsonfilenobuf), "%d",
			 fc_syslogger_fdget(jsonlogFile));
	fc_av[fc_ac++] = fc_jsonfilenobuf;

	fc_av[fc_ac] = NULL;
	Assert(fc_ac < lengthof(fc_av));

	return postmaster_forkexec(fc_ac, fc_av);
}

/*
 * syslogger_parseArgs() -
 *
 * 从exec的syslogger进程的参数列表中提取数据。
 */
static void fc_syslogger_parseArgs(int fc_argc, char *fc_argv[])
{
	int			fc_fd;

	Assert(fc_argc == 6);
	fc_argv += 3;

	/*
	 * 重新打开SysLogger_Start()打开的错误输出文件。
	 *
	 * 我们期望这总是成功，尽管这太过乐观，但如果失败了，我们
	 * 也没有太多办法来报告问题。按照代码，我们将在失败后由于空指针解引用而崩溃...
	 */
	fc_fd = atoi(*fc_argv++);
	syslogFile = fc_syslogger_fdopen(fc_fd);
	fc_fd = atoi(*fc_argv++);
	csvlogFile = fc_syslogger_fdopen(fc_fd);
	fc_fd = atoi(*fc_argv++);
	jsonlogFile = fc_syslogger_fdopen(fc_fd);
}
#endif							/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果仍然附加。这不是旨在由最初创建该段的进程显式调用（它将注册 on_shmem_exit 回调来做到这一点）。相反，这是为那些继承了附加并希望摆脱它的子进程而设。
 *
 * UsedShmemSegID 和 UsedShmemSegAddr 也是该例程的隐式参数，还有 AnonymousShmem 和 AnonymousShmemSize。
 */


/* --------------------------------
 *		管道协议处理
 * --------------------------------
 */

/*
 * 处理通过syslogger管道接收的数据。
 *
 * 此例程解释日志管道协议，该协议将日志消息作为
 * （希望是原子）块发送——这样的块在这里被检测和重新组装。
 *
 * 协议有一个头部，以两个null字节开始，然后是16位
 * 长度、发送进程的pid和一个标志，用于指示它是否为
 * 消息中的最后一块。未完成的块被保存，直到我们读取更多数据，
 * 非最终的块会累积，直到我们得到最终的块。
 *
 * 所有这些都是为了避免两个问题：
 * . 部分消息被写入日志文件（影响轮换），以及
 * . 来自不同后端的消息被交错（消息混乱）。
 *
 * 任何非协议消息都直接写出。这些应该只来自
 * 非PostgreSQL源（例如，第三方库写入
 * stderr）。
 *
 * logbuffer是数据输入缓冲区，*bytes_in_logbuffer是
 * 当前存在的字节数。在退出时，任何尚未处理的数据在
 * logbuffer中左对齐，并更新*bytes_in_logbuffer。
 */
static void fc_process_pipe_input(char *fc_logbuffer, int *fc_bytes_in_logbuffer)
{
	char	   *fc_cursor = fc_logbuffer;
	int			fc_count = *fc_bytes_in_logbuffer;
	int			fc_dest = LOG_DESTINATION_STDERR;
#ifdef FDD
	bool		fc_needFlushAuditFile = false;
#endif

	/* 当我们有足够的头部时，处理数据... */
	while (fc_count >= (int) (offsetof(PipeProtoHeader, data) + 1))
	{
		PipeProtoHeader fc_p;
		int			fc_chunklen;
		bits8		fc_dest_flags;

		/* 我们有有效的头部吗？ */
		memcpy(&fc_p, fc_cursor, offsetof(PipeProtoHeader, data));
		fc_dest_flags = fc_p.flags & (PIPE_PROTO_DEST_STDERR |
								PIPE_PROTO_DEST_CSVLOG |
#ifdef FDD
								PIPE_PROTO_DEST_AUDIT |
#endif //FDD
								PIPE_PROTO_DEST_JSONLOG);
		if (fc_p.nuls[0] == '\0' && fc_p.nuls[1] == '\0' &&
			fc_p.len > 0 && fc_p.len <= PIPE_MAX_PAYLOAD &&
			fc_p.pid != 0 &&
			pg_popcount((char *) &fc_dest_flags, 1) == 1)
		{
			List	   *fc_buffer_list;
			ListCell   *fc_cell;
			save_buffer *fc_existing_slot = NULL,
					   *fc_free_slot = NULL;
			StringInfo	fc_str;

			fc_chunklen = PIPE_HEADER_SIZE + fc_p.len;

			/* 如果我们还没有完整的块，则退出循环 */
			if (fc_count < fc_chunklen)
				break;

			if ((fc_p.flags & PIPE_PROTO_DEST_STDERR) != 0)
				fc_dest = LOG_DESTINATION_STDERR;
			else if ((fc_p.flags & PIPE_PROTO_DEST_CSVLOG) != 0)
				fc_dest = LOG_DESTINATION_CSVLOG;
			else if ((fc_p.flags & PIPE_PROTO_DEST_JSONLOG) != 0)
				fc_dest = LOG_DESTINATION_JSONLOG;
#ifdef FDD
			else if ((fc_p.flags & PIPE_PROTO_DEST_AUDIT) != 0)
			{
				fc_dest = LOG_DESTINATION_AUDIT;
				fc_needFlushAuditFile = (fc_needFlushAuditFile | (fc_p.flags & PIPE_PROTO_IS_LAST));
			}
#endif
			else
			{
				/* 由于头部验证，这不应该发生 */
				Assert(false);
			}

			/* 找到与该源pid对应的任何现有缓冲区 */
			fc_buffer_list = buffer_lists[fc_p.pid % NBUFFER_LISTS];
			foreach(fc_cell, fc_buffer_list)
			{
				save_buffer *fc_buf = (save_buffer *) lfirst(fc_cell);

				if (fc_buf->pid == fc_p.pid)
				{
					fc_existing_slot = fc_buf;
					break;
				}
				if (fc_buf->pid == 0 && fc_free_slot == NULL)
					fc_free_slot = fc_buf;
			}

			if ((fc_p.flags & PIPE_PROTO_IS_LAST) == 0)
			{
				/*
				 * 将完整的非最终块保存在按pid划分的缓冲区中
				 */
				if (fc_existing_slot != NULL)
				{
					/* 将块添加到来自先前块的数据中 */
					fc_str = &(fc_existing_slot->data);
					appendBinaryStringInfo(fc_str,
										   fc_cursor + PIPE_HEADER_SIZE,
										   fc_p.len);
				}
				else
				{
					/* 消息的第一块，保存在一个新缓冲区中 */
					if (fc_free_slot == NULL)
					{
						/*
						 * 需要一个空闲槽，但列表中没有一个，
						 * 因此创建一个新槽并扩展列表。
						 */
						fc_free_slot = palloc(sizeof(save_buffer));
						fc_buffer_list = lappend(fc_buffer_list, fc_free_slot);
						buffer_lists[fc_p.pid % NBUFFER_LISTS] = fc_buffer_list;
					}
					fc_free_slot->pid = fc_p.pid;
					fc_str = &(fc_free_slot->data);
					initStringInfo(fc_str);
					appendBinaryStringInfo(fc_str,
										   fc_cursor + PIPE_HEADER_SIZE,
										   fc_p.len);
				}
			}
			else
			{
				/*
				 * 最终块——将其添加到为该pid保存的任何内容中，并
				 * 无论如何写出整个内容。
				 */
				if (fc_existing_slot != NULL)
				{
					fc_str = &(fc_existing_slot->data);
					appendBinaryStringInfo(fc_str,
										   fc_cursor + PIPE_HEADER_SIZE,
										   fc_p.len);
					write_syslogger_file(fc_str->data, fc_str->len, fc_dest);
					/* 将缓冲区标记为未使用，并回收字符串存储 */
					fc_existing_slot->pid = 0;
					pfree(fc_str->data);
				}
				else
				{
					/* 整个消息显然是一个块。 */
					write_syslogger_file(fc_cursor + PIPE_HEADER_SIZE, fc_p.len,
										 fc_dest);
				}
			}

			/* 完成此块的处理 */
			fc_cursor += fc_chunklen;
			fc_count -= fc_chunklen;
		}
		else
		{
			/* 处理非协议数据 */

			/*
			 * 查找协议头的开始。如果找到，则将数据转储
			 * 到那里并重复循环。否则，将其全部转储并
			 * 退出循环。（注意：如果可能的话，我们希望将其全部转储，
			 * 以避免将非协议消息分散到多个日志文件中。
			 * 我们预计在许多场景中，非协议
			 * 消息会在一个read()中全部到达，我们希望尽可能尊重
			 * read()边界。）
			 */
			for (fc_chunklen = 1; fc_chunklen < fc_count; fc_chunklen++)
			{
				if (fc_cursor[fc_chunklen] == '\0')
					break;
			}
			/* 回退到stderr日志作为目标 */
			write_syslogger_file(fc_cursor, fc_chunklen, LOG_DESTINATION_STDERR);
			fc_cursor += fc_chunklen;
			fc_count -= fc_chunklen;
		}
	}

	/* 我们没有一个完整的块，因此将缓冲区中剩余的内容左对齐 */
	if (fc_count > 0 && fc_cursor != fc_logbuffer)
		memmove(fc_logbuffer, fc_cursor, fc_count);
	*fc_bytes_in_logbuffer = fc_count;
#ifdef FDD
	if(fc_needFlushAuditFile && NULL != g_fdbAuditFile)
	{
		fflush(g_fdbAuditFile);
	}
#endif //FDD
}

/*
 * 强制输出任何缓冲的数据
 *
 * 这目前仅在syslogger关闭时使用，但在其他时间也可能有用，
 * 所以它谨慎地保持了清洁的状态。
 */
static void fc_flush_pipe_input(char *fc_logbuffer, int *fc_bytes_in_logbuffer)
{
	int			fc_i;

	/* 转储任何不完整的协议消息 */
	for (fc_i = 0; fc_i < NBUFFER_LISTS; fc_i++)
	{
		List	   *fc_list = buffer_lists[fc_i];
		ListCell   *fc_cell;

		foreach(fc_cell, fc_list)
		{
			save_buffer *fc_buf = (save_buffer *) lfirst(fc_cell);

			if (fc_buf->pid != 0)
			{
				StringInfo	fc_str = &(fc_buf->data);

				write_syslogger_file(fc_str->data, fc_str->len,
									 LOG_DESTINATION_STDERR);
				/* 将缓冲区标记为未使用，并回收字符串存储 */
				fc_buf->pid = 0;
				pfree(fc_str->data);
			}
		}
	}

	/*
	 * 强制将任何剩余的管道数据原样输出；我们不试图去除其中可能存在的
	 * 协议头部。
	 */
	if (*fc_bytes_in_logbuffer > 0)
		write_syslogger_file(fc_logbuffer, *fc_bytes_in_logbuffer,
							 LOG_DESTINATION_STDERR);
	*fc_bytes_in_logbuffer = 0;
}


/* --------------------------------
 *		日志文件例程
 * --------------------------------
 */

/*
 * 将文本写入当前打开的日志文件
 *
 * 这是导出的，以便elog.c在MyBackendType为B_LOGGER时可以调用它。
 * 这允许syslogger进程记录自己的elog消息，即使它的stderr并未指向
 * syslog管道。
 */
void write_syslogger_file(const char *fc_buffer, int fc_count, int fc_destination)
{
	int			fc_rc;
	FILE	   *fc_logfile;

	/*
	 * 如果我们被告知要写入一个结构化日志文件，但它未打开，
	 * 则将数据转储到syslogFile（该文件始终打开）。这可能发生
	 * 在postmaster启动后启用结构化输出且我们无法打开logFile时。
	 * 在参数更改期间也存在竞争条件，后端可能在我们打开logFile
	 * 之前或关闭后向我们发送结构化输出。在常规日志文件中写入
	 * 格式化输出并不理想，但这比将日志输出掉在地上要好。
	 *
	 * 别想通过尝试动态打开logFile来改善这一切。任何失败将导致
	 * 递归。
	 */
#ifdef FDD
	if(LOG_DESTINATION_AUDIT == fc_destination)
	{
		fc_rc = 0;
		if(g_fdbAuditFile != NULL)
		{
			if(FDB_IS_AYDIT_ENCRYPT(g_fdbAuditLogEncryptType))
			{
				fdb_encrypt(&g_fdbRc4State, (unsigned char*)fc_buffer, (unsigned char*)fc_buffer, fc_count);
			}			
			fc_rc = fwrite(fc_buffer, 1, fc_count, g_fdbAuditFile);
		}
	}
	else
	{
#endif
	if ((fc_destination & LOG_DESTINATION_CSVLOG) && csvlogFile != NULL)
		fc_logfile = csvlogFile;
	else if ((fc_destination & LOG_DESTINATION_JSONLOG) && jsonlogFile != NULL)
		fc_logfile = jsonlogFile;
	else
		fc_logfile = syslogFile;

	fc_rc = fwrite(fc_buffer, 1, fc_count, fc_logfile);
#ifdef FDD
	}
#endif
	/*
	 * 尝试报告任何失败。我们不能使用ereport，因为它会直接递归回
	 * 这里，但write_stderr是可以的：它会写入到postmaster的原始
	 * stderr，或写入/dev/null，但绝不会写入我们的输入管道，
	 * 这样会导致另一种循环。
	 */
	if (fc_rc != fc_count)
		write_stderr("could not write to log file: %s\n", strerror(errno));
}

#ifdef WIN32

/*
 * 工作者线程用于将数据从管道转移到当前日志文件。
 *
 * 我们需要这个，因为在Windows上，WaitForMultipleObjects不适用于
 * 无名管道：它总是报告“已信号”，所以阻塞的ReadFile不允许
 * SIGHUP；而select仅适用于套接字。
 */
static unsigned int __stdcall
pipeThread(void *arg)
{
	char		logbuffer[READ_BUF_SIZE];
	int			bytes_in_logbuffer = 0;

	for (;;)
	{
		DWORD		bytesRead;
		BOOL		result;

		result = ReadFile(syslogPipe[0],
						  logbuffer + bytes_in_logbuffer,
						  sizeof(logbuffer) - bytes_in_logbuffer,
						  &bytesRead, 0);

		/*
		 * 在做任何可能影响主线程共享的全局状态的操作之前
		 * 进入临界区。特别是，使用palloc()/pfree()的任何操作
		 * 在临界区之外是不安全的。
		 */
		EnterCriticalSection(&sysloggerSection);
		if (!result)
		{
			DWORD		error = GetLastError();

			if (error == ERROR_HANDLE_EOF ||
				error == ERROR_BROKEN_PIPE)
				break;
			_dosmaperr(error);
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not read from logger pipe: %m")));
		}
		else if (bytesRead > 0)
		{
			bytes_in_logbuffer += bytesRead;
			process_pipe_input(logbuffer, &bytes_in_logbuffer);
		}

		/*
		 * 如果我们填满了当前日志文件，提示主线程进行日志轮换。
		 */
		if (Log_RotationSize > 0)
		{
			if (ftell(syslogFile) >= Log_RotationSize * 1024L ||
				(csvlogFile != NULL && ftell(csvlogFile) >= Log_RotationSize * 1024L) ||
				(jsonlogFile != NULL && ftell(jsonlogFile) >= Log_RotationSize * 1024L))
				SetLatch(MyLatch);
		}
		LeaveCriticalSection(&sysloggerSection);
	}

	/* 我们只在检测到管道EOF时退出上述循环 */
	pipe_eof_seen = true;

	/* 如果还有任何数据，则立即强制输出 */
	flush_pipe_input(logbuffer, &bytes_in_logbuffer);

	/* 设置闩锁以唤醒主线程，主线程将退出 */
	SetLatch(MyLatch);

	LeaveCriticalSection(&sysloggerSection);
	_endthread();
	return 0;
}
#endif							/* WIN32 */

/*
 * 以适当的权限和缓冲选项打开新的日志文件。
 *
 * 如果allow_errors为true，我们只记录任何打开失败并返回NULL
 * （fopen失败时errno仍然正确）。
 * 否则，错误将被视为致命。
 */
static FILE * fc_logfile_open(const char *fc_filename, const char *fc_mode, bool fc_allow_errors)
{
	FILE	   *fc_fh;
	mode_t		fc_oumask;

	/*
	 * 请注意，我们不允许Log_file_mode禁用IWUSR，因为我们当然
	 * 希望能够自己写入文件。
	 */
	fc_oumask = umask((mode_t) ((~(Log_file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO)));
	fc_fh = fopen(fc_filename, fc_mode);
	umask(fc_oumask);

	if (fc_fh)
	{
		setvbuf(fc_fh, NULL, PG_IOLBF, 0);

#ifdef WIN32
		/* 在Windows上使用CRLF行结束符 */
		_setmode(_fileno(fh), _O_TEXT);
#endif
	}
	else
	{
		int			fc_save_errno = errno;

		ereport(fc_allow_errors ? LOG : FATAL,
				(errcode_for_file_access(),
				 errmsg("could not open log file \"%s\": %m",
						fc_filename)));
		errno = fc_save_errno;
	}

	return fc_fh;
}

/*
 * 根据target_dest执行日志文件轮换。
 * 在成功的文件轮换中，*last_file_name和*logFile中的信息将被更新。
 *
 * 如果轮换已停止，则返回false，否则返回true以继续处理其他格式。
 */
static bool fc_logfile_rotate_dest(bool fc_time_based_rotation, int fc_size_rotation_for,
					pg_time_t fc_fntime, int fc_target_dest,
					char **fc_last_file_name, FILE **fc_logFile)
{
	char	   *fc_logFileExt = NULL;
	char	   *fc_filename;
	FILE	   *fc_fh;

	/*
	 * 如果目标目的地刚刚关闭，则关闭上一个文件并注销其数据。
	 * 由于syslogFile假定始终打开，因此这对于stderr是不可能发生的，
	 * 即使在log_destination中禁用stderr。
	 */
	if ((Log_destination & fc_target_dest) == 0 &&
		fc_target_dest != LOG_DESTINATION_STDERR)
	{
		if (*fc_logFile != NULL)
			fclose(*fc_logFile);
		*fc_logFile = NULL;
		if (*fc_last_file_name != NULL)
			pfree(*fc_last_file_name);
		*fc_last_file_name = NULL;
		return true;
	}

	/*
	 * 如果尚未到达轮换时间，或如果目标目的地无需根据文件大小
	 * 进行轮换，则离开。
	 */
	if (!fc_time_based_rotation && (fc_size_rotation_for & fc_target_dest) == 0)
		return true;

	/* 文件扩展名取决于目标类型 */
	if (fc_target_dest == LOG_DESTINATION_STDERR)
		fc_logFileExt = NULL;
	else if (fc_target_dest == LOG_DESTINATION_CSVLOG)
		fc_logFileExt = ".csv";
	else if (fc_target_dest == LOG_DESTINATION_JSONLOG)
		fc_logFileExt = ".json";
	else
	{
		/* 不会发生 */
		Assert(false);
	}

	/* 构建新文件名 */
	fc_filename = fc_logfile_getname(fc_fntime, fc_logFileExt);

	/*
	 * 决定是覆盖还是附加。如果满足以下条件，我们可以覆盖：
	 * (a) Log_truncate_on_rotation已设置，(b) 轮换是由
	 * 经过时间触发而不是其他事物，(c) 计算出的文件名与我们
	 * 之前记录的不同。
	 */
	if (Log_truncate_on_rotation && fc_time_based_rotation &&
		*fc_last_file_name != NULL &&
		strcmp(fc_filename, *fc_last_file_name) != 0)
		fc_fh = fc_logfile_open(fc_filename, "w", true);
	else
		fc_fh = fc_logfile_open(fc_filename, "a", true);

	if (!fc_fh)
	{
		/*
		 * 在繁忙的系统上，ENFILE/EMFILE并不令人惊讶；继续使用旧文件
		 *，直到我们设法获取新文件。否则，假设Log_directory存在问题，
		 * 并停止尝试创建文件。
		 */
		if (errno != ENFILE && errno != EMFILE)
		{
			ereport(LOG,
					(errmsg("disabling automatic rotation (use SIGHUP to re-enable)")));
			rotation_disabled = true;
		}

		if (fc_filename)
			pfree(fc_filename);
		return false;
	}

	/* 填写新的信息 */
	if (*fc_logFile != NULL)
		fclose(*fc_logFile);
	*fc_logFile = fc_fh;

	/* 不要释放filename，而是记住它以备下次使用 */
	if (*fc_last_file_name != NULL)
		pfree(*fc_last_file_name);
	*fc_last_file_name = fc_filename;

	return true;
}

/*
 * 执行日志文件轮换
 */
static void fc_logfile_rotate(bool fc_time_based_rotation, int fc_size_rotation_for)
{
	pg_time_t	fc_fntime;

	rotation_requested = false;

	/*
	 * 在执行时间基础的轮换时，根据计划的轮换时间，而不是当前时间，
	 * 发明新的日志文件名，以避免在我们不立即进行轮换时“滑动”
	 * 文件名。
	 */
	if (fc_time_based_rotation)
		fc_fntime = next_rotation_time;
	else
		fc_fntime = time(NULL);

	/* stderr的文件轮换 */
	if (!fc_logfile_rotate_dest(fc_time_based_rotation, fc_size_rotation_for, fc_fntime,
							 LOG_DESTINATION_STDERR, &last_sys_file_name,
							 &syslogFile))
		return;

	/* csvlog的文件轮换 */
	if (!fc_logfile_rotate_dest(fc_time_based_rotation, fc_size_rotation_for, fc_fntime,
							 LOG_DESTINATION_CSVLOG, &last_csv_file_name,
							 &csvlogFile))
		return;

	/* jsonlog的文件轮换 */
	if (!fc_logfile_rotate_dest(fc_time_based_rotation, fc_size_rotation_for, fc_fntime,
							 LOG_DESTINATION_JSONLOG, &last_json_file_name,
							 &jsonlogFile))
		return;

	update_metainfo_datafile();

	set_next_rotation_time();
}


/*
 * 使用时间戳信息构造日志文件名
 *
 * 如果后缀不为NULL，将其附加到名称中，替换模式中可能出现的
 * 任何“.log”。
 *
 * 结果是palloc的。
 */
static char * fc_logfile_getname(pg_time_t fc_timestamp, const char *fc_suffix)
{
	char	   *fc_filename;
	int			fc_len;

	fc_filename = palloc(MAXPGPATH);

	snprintf(fc_filename, MAXPGPATH, "%s/", Log_directory);

	fc_len = strlen(fc_filename);

	/* 将Log_filename视为strftime模式 */
	pg_strftime(fc_filename + fc_len, MAXPGPATH - fc_len, Log_filename,
				pg_localtime(&fc_timestamp, log_timezone));

	if (fc_suffix != NULL)
	{
		fc_len = strlen(fc_filename);
		if (fc_len > 4 && (strcmp(fc_filename + (fc_len - 4), ".log") == 0))
			fc_len -= 4;
		strlcpy(fc_filename + fc_len, fc_suffix, MAXPGPATH - fc_len);
	}

	return fc_filename;
}

/*
 * 确定下一个计划的轮换时间，并存储在next_rotation_time中。
 */
static void set_next_rotation_time(void)
{
	pg_time_t	fc_now;
	struct pg_tm *fc_tm;
	int			fc_rotinterval;

	/* 如果基于时间的旋转被禁用，则无需执行任何操作 */
	if (Log_RotationAge <= 0)
		return;

	/*
	 * 这里的要求是选择下一个时间 > 现在，它是日志旋转间隔的
	 * “倍数”。 “倍数”可以相对宽松地解释。 在这个版本中，我们对齐到 log_timezone 而不是
	 * GMT。
	 */
	fc_rotinterval = Log_RotationAge * SECS_PER_MINUTE;	/* 转换为秒 */
	fc_now = (pg_time_t) time(NULL);
	fc_tm = pg_localtime(&fc_now, log_timezone);
	fc_now += fc_tm->tm_gmtoff;
	fc_now -= fc_now % fc_rotinterval;
	fc_now += fc_rotinterval;
	fc_now -= fc_tm->tm_gmtoff;
	next_rotation_time = fc_now;
}

/*
 * 存储日志收集器的文件名，当启用时，它会写入
 * 日志消息。 这在基于时间的日志文件旋转时，方便查找当前日志文件的名称。 文件名
 * 存储在临时文件中，并重命名为最终目标以确保
 * 原子性。 该文件以与数据目录中创建的
 * 相同的权限打开，并具有适当的缓冲选项。
 */
static void update_metainfo_datafile(void)
{
	FILE	   *fc_fh;
	mode_t		fc_oumask;

	if (!(Log_destination & LOG_DESTINATION_STDERR) &&
		!(Log_destination & LOG_DESTINATION_CSVLOG) &&
		!(Log_destination & LOG_DESTINATION_JSONLOG))
	{
		if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m",
							LOG_METAINFO_DATAFILE)));
		return;
	}

	/* 对于新文件使用与数据目录相同的权限 */
	fc_oumask = umask(pg_mode_mask);
	fc_fh = fopen(LOG_METAINFO_DATAFILE_TMP, "w");
	umask(fc_oumask);

	if (fc_fh)
	{
		setvbuf(fc_fh, NULL, PG_IOLBF, 0);

#ifdef WIN32
		/* 在Windows上使用CRLF行结束符 */
		_setmode(_fileno(fh), _O_TEXT);
#endif
	}
	else
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m",
						LOG_METAINFO_DATAFILE_TMP)));
		return;
	}

	if (last_sys_file_name && (Log_destination & LOG_DESTINATION_STDERR))
	{
		if (fprintf(fc_fh, "stderr %s\n", last_sys_file_name) < 0)
		{
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							LOG_METAINFO_DATAFILE_TMP)));
			fclose(fc_fh);
			return;
		}
	}

	if (last_csv_file_name && (Log_destination & LOG_DESTINATION_CSVLOG))
	{
		if (fprintf(fc_fh, "csvlog %s\n", last_csv_file_name) < 0)
		{
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							LOG_METAINFO_DATAFILE_TMP)));
			fclose(fc_fh);
			return;
		}
	}

	if (last_json_file_name && (Log_destination & LOG_DESTINATION_JSONLOG))
	{
		if (fprintf(fc_fh, "jsonlog %s\n", last_json_file_name) < 0)
		{
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							LOG_METAINFO_DATAFILE_TMP)));
			fclose(fc_fh);
			return;
		}
	}
	fclose(fc_fh);

	if (rename(LOG_METAINFO_DATAFILE_TMP, LOG_METAINFO_DATAFILE) != 0)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						LOG_METAINFO_DATAFILE_TMP, LOG_METAINFO_DATAFILE)));
}

/* --------------------------------
 *		信号处理程序例程
 * --------------------------------
 */

/*
 * 检查日志旋转请求是否到达。 应该由 postmaster 在接收到 SIGUSR1 后调用。
 */
bool CheckLogrotateSignal(void)
{
	struct stat fc_stat_buf;

	if (stat(LOGROTATE_SIGNAL_FILE, &fc_stat_buf) == 0)
		return true;

	return false;
}

/*
 * 删除表示日志旋转请求的文件。
 */
void RemoveLogrotateSignalFiles(void)
{
	unlink(LOGROTATE_SIGNAL_FILE);
}

/* SIGUSR1：设置标志以旋转日志文件 */
static void fc_sigUsr1Handler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	rotation_requested = true;
	SetLatch(MyLatch);

	errno = fc_save_errno;
}
